package cc.shacocloud.mirage.restful.bind.support;

import cc.shacocloud.mirage.restful.AbstractHandlerExceptionResolver;
import cc.shacocloud.mirage.restful.HttpRequest;
import cc.shacocloud.mirage.restful.HttpResponse;
import cc.shacocloud.mirage.restful.VertxInvokeHandler;
import cc.shacocloud.mirage.restful.exception.*;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.util.Date;
import java.util.Objects;
import java.util.Optional;

/**
 * 抽象的默认处理异常解析器
 *
 * @author 思追(shaco)
 */
@Slf4j
public abstract class AbstractDefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
    
    /**
     * 解析异常信息，基于不同的异常写入响应体不同的状态消息
     */
    protected void parseException(@NotNull HttpResponse response,
                                  @NotNull Throwable cause) {
        HttpResponseStatus responseStatus;
        
        // 参数不符合格式
        if (cause instanceof BindingException || cause instanceof MethodArgumentNotValidException) {
            responseStatus = HttpResponseStatus.UNPROCESSABLE_ENTITY;
        }
        // 无效的请求
        else if (cause instanceof HttpMediaTypeException || cause instanceof HttpMessageNotReadableException
                || cause instanceof InvalidMediaTypeException || cause instanceof InvalidMimeTypeException
                || cause instanceof InvalidRequestPathException || cause instanceof HttpRequestBindMissingException) {
            responseStatus = HttpResponseStatus.BAD_REQUEST;
        }
        // 资源不存在异常
        else if (cause instanceof ResourceNotFoundException) {
            responseStatus = HttpResponseStatus.NOT_FOUND;
        }
        // 指定状态异常
        else if (cause instanceof HttpStatusException) {
            responseStatus = ((HttpStatusException) cause).getStatus();
        }
        // 其他异常
        else {
            responseStatus = HttpResponseStatus.INTERNAL_SERVER_ERROR;
        }
        
        response.setStatusCode(responseStatus.code());
        response.setStatusMessage(responseStatus.reasonPhrase());
    }
    
    @Override
    protected Future<Object> doResolveException(@NotNull HttpRequest request,
                                                @NotNull HttpResponse response,
                                                @NotNull VertxInvokeHandler handler,
                                                @NotNull Throwable cause) {
        if (log.isErrorEnabled()) {
            log.error("", cause);
        }
        
        // 如果未设置状态码则根据异常匹配对应的状态码
        int statusCode = response.getStatusCode();
        if (200 <= statusCode && statusCode < 300) {
            parseException(response, cause);
        }
        
        // 统一格式
        DefaultExceptionMo defaultExceptionMo = new DefaultExceptionMo();
        defaultExceptionMo.setPath(request.context().normalizedPath());
        defaultExceptionMo.setStatus(response.getStatusCode());
        defaultExceptionMo.setStatusMessage(response.getStatusMessage());
        defaultExceptionMo.setErrorMsg(Optional.ofNullable(cause.getMessage()).orElse(""));
        defaultExceptionMo.setCurrentDateTime(new Date().toString());
        
        return doResolveException(request, response, handler, cause, defaultExceptionMo)
                .compose(res -> {
                    
                    // 如果结果不为空则写入响应
                    if (Objects.nonNull(res)) {
                        Promise<Void> promise = Promise.promise();
                        response.end(res, promise);
                        return promise.future().map(r -> FINISH);
                    }
                    
                    return Future.succeededFuture(FINISH);
                });
    }
    
    /**
     * 在原有 {@link #doResolveException(HttpRequest, HttpResponse, VertxInvokeHandler, Throwable)} 的基础上，新增 {@link DefaultExceptionMo}
     * 模型，以方便子类统一格式
     */
    protected abstract Future<Buffer> doResolveException(@NotNull HttpRequest request,
                                                         @NotNull HttpResponse response,
                                                         @NotNull VertxInvokeHandler handler,
                                                         @NotNull Throwable cause,
                                                         @NotNull DefaultExceptionMo defaultExceptionMo);
    
    @Setter
    @Getter
    @NoArgsConstructor
    protected static class DefaultExceptionMo {
        
        private String path;
        private int status;
        private String statusMessage;
        private String errorMsg;
        private String currentDateTime;
        
    }
    
}
